﻿#region Using Statements

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

#endregion


namespace Freeform
{

    #region CollisionProperties

    /// <summary>
    /// Collision properties define whether or not an entity collides with others, which entities they collide with and what method
    /// of collision detection will be used.
    /// </summary>
    public class CollisionProperties
    {

        #region Events

        /// <summary>
        /// This event is fired when the entity that owns these collision properties collides with another entity.
        /// </summary>
        public event CollisionOccuredEventHandler CollisionOccured;
        public delegate void CollisionOccuredEventHandler(Entity thisEntity, Entity otherEnt);

        #endregion


        #region Members

        /// <summary>
        /// The collision ID determines how other entities interact with this one according to the other entity's collision mask.
        /// </summary>
        public virtual Byte CollisionID
        {
            get { return _CollisionID; }
            set
            {
                if (value > 63) throw new Exception("A collision ID must be between 0 and 63 (inclusively.)");
                _CollisionID = value;
            }
        }
        private Byte _CollisionID = 0;


        /// <summary>
        /// The collision mask is a 64-bit unsigned integer. Each bit represents a boolean value as to whether or not this
        /// entity collides with an entity of a given CollisionID.
        /// Example: A collision mask of: 01001100 00000000 ..... = 50 would mean that the entity collides with other entities
        /// with CollisionID of 1, 4 or 5.
        /// Use SetCollisionMask() to set whether or not this entity collides with an entity of a given CollisionID.
        /// </summary>
        public UInt64 CollisionMask { get; private set; }


        /// <summary>
        /// The collision type variable determines the method used for detecting collisions for this entity.
        /// </summary>
        public CollisionType CollisionType { get; set; }


        /// <summary>
        /// Gets or sets the collision geometry used for collision detection.
        /// </summary>
        public CollisionGeometry Geometry { get; internal set; }


        /// <summary>
        /// Gets the entity that these collision properties refer to.
        /// </summary>
        public Entity Entity { get; private set; }


        /// <summary>
        /// Gets or sets the collision handler that is invoked when a collision is detected.
        /// </summary>
        public CollisionHandler Handler { get; set; }

        #endregion


        #region Init

        /// <summary>
        /// Creates a new set of collision properties.
        /// </summary>
        /// <param name="entity">The entity to which the properties pertain.</param>
        public CollisionProperties(Entity entity)
        {
            Entity = entity;
            Entity.CollisionProperties = this;
            CollisionType = CollisionType.None;
        }

        #endregion


        #region Utility

        /// <summary>
        /// Specifies that this entity will collide with another entity with the specified collisionID if value is set to true.
        /// No such collision will occur if the value is set to false.
        /// </summary>
        /// <param name="id">The CollisionID.</param>
        /// <param name="value">True to enforce collisions, false otherwise.</param>
        public virtual void SetCollisionMask(Byte id, Boolean value)
        {
            if (id > 63)
                throw new Exception("A collision ID must be between 0 and 63 (inclusively.)");

            UInt64 mask = ((UInt64)1 << id);

            if (value)
                CollisionMask |= mask;
            else
            {
                mask = ~mask;
                CollisionMask &= mask;
            }
        }


        /// <summary>
        /// Checks whether or not the two collision properties collide.
        /// </summary>
        /// <param name="other">The collision property of the other entity.</param>
        /// <returns>True if and only if a collision occurs.</returns>
        public virtual Boolean CheckCollision(CollisionProperties other)
        {
            if (Entity.Disposed || other.Entity.Disposed) return false;

            // Check if collision masks don't match up.
            if ((CollisionMask >> other.CollisionID) % 2 == 0) return false;

            // Check for geometric collision.
            return Geometry.CheckCollision(other.Geometry);
        }


        /// <summary>
        /// Causes the collision handler to be invoked for this entity.
        /// </summary>
        /// <param name="otherEntity">The entity with which we have collided.</param>
        /// <param name="mutual">If true, this will cause the other entity's collision handler to be invoked also.</param>
        public virtual void FireCollision(Entity otherEntity, Boolean mutual) 
        {
            // Allow handler to handle collision.
            if (Handler != null)
                Handler.HandleCollision(otherEntity);

            // Fire collision event.
            if (CollisionOccured != null)
                CollisionOccured(Entity, otherEntity);

            // Fire mutual collision.
            if (mutual)
                FireCollision(Entity, false);
        }

        #endregion

    }

    #endregion


    #region CollisionType

    /// <summary>
    /// Determines the type of collision tests that the entity will perform.
    /// </summary>
    public enum CollisionType
    {
        /// <summary>
        /// Entities with active collision will actively check if they collide with any other Active or Passive entities. Flagging too many
        /// entities as active collision type can result in poor performance.
        /// </summary>
        Active,

        /// <summary>
        /// Passive collision checking means that they are only tested against active collision entities. Passive entities can't collide with
        /// other passive entities. This saves a lot of CPU time and is suitable for entities that do not move.
        /// </summary>
        Passive,

        /// <summary>
        /// Entities with collision type none don't collide with anything regardless of their CollisionID and CollisionMask settings.
        /// </summary>
        None
    }

    #endregion


    #region CollisionGeometry

    /// <summary>
    /// A CollisionGeometry class performs the actual geometric collision test against another CollisionGeometry class.
    /// This class should be extended by classes such as CollisionGeometry_Circular, CollisionGeometry_LineSegment etc.
    /// </summary>
    public class CollisionGeometry
    {

        #region Members

        /// <summary>
        /// Gets the entity that this collision geometry represents.
        /// </summary>
        public Entity Entity { get; protected set; }

        #endregion


        #region Initialisation

        /// <summary>
        /// Creates a new collision geometry class.
        /// </summary>
        /// <param name="entity">The entity for which the geometry represents.</param>
        public CollisionGeometry(Entity entity)
        {
            Entity = entity;
            Entity.CollisionProperties.Geometry = this;
        }

        #endregion


        #region Utility

        /// <summary>
        /// Checks whether or not the two collision properties collide.
        /// </summary>
        /// <param name="other">The collision property of the other entity.</param>
        /// <returns>True if and only if the two geometries collide.</returns>
        public virtual Boolean CheckCollision(CollisionGeometry other) { return false; }

        #endregion

    }

    #endregion


    #region CollisionHandler

    /// <summary>
    /// Collision Handlers are invoked when a collision is detected.
    /// </summary>
    public class CollisionHandler
    {

        #region Members

        /// <summary>
        /// Gets the collision properties that this handler works for.
        /// </summary>
        public virtual CollisionProperties CollisionProperties { get; private set; }


        /// <summary>
        /// Gets the entity that these collision properties refer to.
        /// </summary>
        public virtual Entity Entity { get; private set; }

        #endregion


        #region Init

        /// <summary>
        /// Creates a new collision handler.
        /// </summary>
        /// <param name="properties">The collision properties that this handler will work for.</param>
        public CollisionHandler(CollisionProperties properties)
        {
            CollisionProperties = properties;
            Entity = CollisionProperties.Entity;
        }

        #endregion


        #region Utility

        /// <summary>
        /// This function is called when the CollisionHandler's entity collides with another entity.
        /// </summary>
        /// <param name="otherEntity">The other entity with which our entity collided.</param>
        public virtual void HandleCollision(Entity otherEntity)
        { }

        #endregion

    }

    #endregion


    #region CollisionChecks

    /// <summary>
    /// Determines the method used to check collisions.
    /// </summary>
    public enum CollisionChecks
    {
        /// <summary>
        /// In two-way collisions, if A collides with B, B does not necessarily collide with A, and both collisions will be checked.
        /// </summary>
        TwoWay,

        /// <summary>
        /// In one-way collisions, if A collides with B, B does not collide with A.
        /// </summary>
        OneWay,


        /// <summary>
        /// In mutual collisions, if A collides with B, B collides with A.
        /// </summary>
        Mutual
    }

    #endregion

}
